package nio.zerocopy;

import org.junit.Test;
import sun.misc.Cleaner;

import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * @Desc:   MappedByteBuffer 对文件进行读写案例
 * @author: cww
 * @DateTime: 2020-02-09 21:10
 */

public class MappedByteBufferTest {
    private final static String CONTENT = "My First Zero Copy Example";
    private final static String FILE_NAME = "/mmap.txt";
    private final static String CHARSET = "UTF-8";


    @Test
    public void  writeToFileByMappedByteBuffer() throws Exception{
        Path path = Paths.get((getClass().getResource(FILE_NAME).getPath()).replaceFirst("/",""));
        System.out.println("写入文件目录：" + path.toString());
        byte[] bytes = CONTENT.getBytes(Charset.forName(CHARSET));
        FileChannel fileChannel = FileChannel.open(path,
                StandardOpenOption.READ,
                StandardOpenOption.WRITE,
                StandardOpenOption.TRUNCATE_EXISTING);
        // 通过 fileChannel 映射到一个可写的内存缓冲区 mappedByteBuffer，将目标写入
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, bytes.length);
        if (mappedByteBuffer != null) {
            // 把目标数据写入 mappedByteBuffer
            mappedByteBuffer.put(bytes);
            // 把缓冲区更改的内容强制写入本地文件
            mappedByteBuffer.force();
            System.out.println("MappedByteBuffer 写入文件成功");
        }
    }

    @Test
    public void readFromFileByMappedByteBuffer() throws Exception {
        Path path = Paths.get((getClass().getResource(FILE_NAME).getPath()).replaceFirst("/",""));
        System.out.println("读取文件目录：" + path.toString());
        int length = CONTENT.getBytes(Charset.forName(CHARSET)).length;
        // 打开文件通道 fileChannel 并提供只读权限
        FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ);
        // 通过 fileChannel 映射到一个只可读的内存缓冲区 mappedByteBuffer
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, length);
        if (mappedByteBuffer != null) {
            byte[] bytes = new byte[length];
            mappedByteBuffer.get(bytes);
            String content = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("读取内容：" + content);

        }
    }

    /**
     * MappedByteBuffer 提供了文件映射内存的 mmap() 方法，也提供了释放映射内存的 unmap() 方法。
     * 然而 unmap() 是 FileChannelImpl 中的私有方法，无法直接显示调用。因此，用户程序需要通过 Java
     * 反射的调用 sun.misc.Cleaner 类的 clean() 方法手动释放映射占用的内存区域
     * @param buffer
     * @throws Exception
     */
    @Test
    public static void clean(final Object buffer) throws Exception {
        AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
            try {
                Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
                getCleanerMethod.setAccessible(true);
                Cleaner cleaner = (Cleaner) getCleanerMethod.invoke(buffer, new Object[0]);
                cleaner.clean();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        });
    }
}
